/*
 ============================================================================
 Name        : f9trace.c
 Author      : kyle.lu@wwwins.com
 Version     :
 Copyright   : (c) 2009
 Description :
 ============================================================================
 */

#define _UNICODE
#define UNICODE
#define _WIN32_WINNT 0x0400
#define _WIN32_IE 0x0500

#include <tchar.h>
#include <windows.h>
#include <windowsx.h>
#include <commctrl.h>
#include <shlwapi.h>
#include "ConvertUTF.h"

#if defined(_MSC_VER)
	#pragma comment(lib, "comctl32.lib")
	#pragma comment(lib, "user32.lib")
	#pragma comment(lib, "ole32.lib")
#endif

#define ID_EDIT 1
#define ID_BUTTON 2
#define BUFFER_BLOCK_SIZE 2048

typedef void (CALLBACK *FileChangeCallback)(LPTSTR, DWORD, LPARAM);

typedef struct tagDIR_MONITOR
{
	OVERLAPPED ol;
	HANDLE     hDir;
	BYTE       buffer[32 * 1024];
	LPARAM     lParam;
	DWORD      notifyFilter;
	BOOL       fStop;
	FileChangeCallback callback;
} *HDIR_MONITOR;

HWND hwndEdit;
HWND hwndButton;
HDIR_MONITOR hmon;

WCHAR *editBuffer = NULL;
unsigned long editBufferLen = 0;
CHAR *fileBuffer = NULL;
unsigned long fileBufferLen = 0;

//HINSTANCE g_hinst;
const TCHAR *logName = TEXT("flashlog.txt");
const TCHAR *logPath = TEXT("\\Macromedia\\Flash Player\\Logs\\");
TCHAR logFullpath[MAX_PATH];
TCHAR logPathname[MAX_PATH];
BOOL working = FALSE;

/*
 * Unpacks events and passes them to a user defined callback.
 */
VOID CALLBACK MonitorCallback(DWORD dwErrorCode, DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped)
{
	TCHAR                    szFile[MAX_PATH];
	PFILE_NOTIFY_INFORMATION pNotify;
	HDIR_MONITOR             pMonitor  = (HDIR_MONITOR) lpOverlapped;
	size_t                   offset    =  0;
	BOOL RefreshMonitoring(HDIR_MONITOR pMonitor);

	if (dwErrorCode == ERROR_SUCCESS)
	{
		do
		{
			pNotify = (PFILE_NOTIFY_INFORMATION) &pMonitor->buffer[offset];
			offset += pNotify->NextEntryOffset;

#			if defined(UNICODE)
			{
			    lstrcpynW(szFile, pNotify->FileName,
			                min(MAX_PATH, pNotify->FileNameLength / sizeof(WCHAR) + 1));
			}
#			else
			{
			    int count = WideCharToMultiByte(CP_ACP, 0, pNotify->FileName,
			                                    pNotify->FileNameLength / sizeof(WCHAR),
			                                    szFile, MAX_PATH - 1, NULL, NULL);
			    szFile[count] = TEXT('\0');
			}
#			endif

			pMonitor->callback(szFile, pNotify->Action, pMonitor->lParam);

		} while (pNotify->NextEntryOffset != 0);
	}

	if (!pMonitor->fStop)
	{
		RefreshMonitoring(pMonitor);
	}
}

/*
 * Refreshes the directory monitoring.
 */
BOOL RefreshMonitoring(HDIR_MONITOR pMonitor)
{
	return
	ReadDirectoryChangesW(pMonitor->hDir, pMonitor->buffer, sizeof(pMonitor->buffer), FALSE,
	                      pMonitor->notifyFilter, NULL, &pMonitor->ol, MonitorCallback);
}

/*
 * Stops monitoring a directory.
 */
void StopMonitoring(HDIR_MONITOR pMonitor)
{
	if (pMonitor)
	{
		pMonitor->fStop = TRUE;

		CancelIo(pMonitor->hDir);

		if (!HasOverlappedIoCompleted(&pMonitor->ol))
		{
			SleepEx(5, TRUE);
		}

		CloseHandle(pMonitor->ol.hEvent);
		CloseHandle(pMonitor->hDir);
		HeapFree(GetProcessHeap(), 0, pMonitor);
	}
}

/*
 * Starts monitoring a directory.
 */
HDIR_MONITOR StartMonitoring(LPCTSTR szDirectory, DWORD notifyFilter, FileChangeCallback callback)
{
	HDIR_MONITOR pMonitor = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*pMonitor));

	pMonitor->hDir = CreateFile(szDirectory, FILE_LIST_DIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
	                            NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);

	if (pMonitor->hDir != INVALID_HANDLE_VALUE)
	{
		pMonitor->ol.hEvent    = CreateEvent(NULL, TRUE, FALSE, NULL);
		pMonitor->notifyFilter = notifyFilter;
		pMonitor->callback     = callback;

		if (RefreshMonitoring(pMonitor))
		{
			return pMonitor;
		}
		else
		{
			CloseHandle(pMonitor->ol.hEvent);
			CloseHandle(pMonitor->hDir);
		}
	}

	HeapFree(GetProcessHeap(), 0, pMonitor);
	return NULL;
}

/*
 * Runs a message loop that allows APCs to be despatched.
 */
int RunAPCMessageLoop(void)
{
	BOOL done = FALSE;
	MSG  msg  = { 0 };

	while (!done)
	{
		/* Wait for either an APC or a message. */
		while (WAIT_IO_COMPLETION ==
		        MsgWaitForMultipleObjectsEx(0, NULL, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE)) /* Do nothing */;

		/* One or more messages have arrived. */
		while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
		{
			if (msg.message == WM_QUIT)
			{
				done = TRUE;
				break;
			}

			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
	}

	return (int) msg.wParam;
}

void CALLBACK FileCallback(LPTSTR szFile, DWORD action, LPARAM lParam)
{
	if(working) return;
	working = TRUE;
	/* Shouldn't call MessageBox as it will block new notifications coming in. */
	//MessageBox(NULL, szFile, NULL, 0);
	HANDLE hFile = CreateFile(logPathname,
							GENERIC_READ,
							FILE_SHARE_READ|FILE_SHARE_WRITE,
							NULL,
							OPEN_EXISTING,
							FILE_ATTRIBUTE_NORMAL,
							NULL);
	DWORD bytesRead = 0;
	if(hFile != INVALID_HANDLE_VALUE) {
		DWORD fSize = GetFileSize(hFile, NULL);
		if(fSize!=0xFFFFFFFF && fSize>fileBufferLen) {
			if(fileBuffer != NULL) HeapFree(GetProcessHeap(), 0, fileBuffer);
			fileBufferLen = (fSize/BUFFER_BLOCK_SIZE+1)*BUFFER_BLOCK_SIZE;
			fileBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, fileBufferLen);
			if(fileBuffer == NULL) fileBufferLen = 0;
		}
		if(fileBuffer != NULL) ReadFile(hFile,
									fileBuffer,
									fSize,
									&bytesRead,
									NULL);
	}
	if(hFile) CloseHandle(hFile);
	SendMessage(hwndEdit, WM_SETREDRAW, (WPARAM)FALSE, 0);
#ifdef UNICODE
	if(bytesRead+1 > editBufferLen) {
		if(editBuffer != NULL) HeapFree(GetProcessHeap(), 0, editBuffer);
		editBufferLen = (bytesRead/BUFFER_BLOCK_SIZE+1)*BUFFER_BLOCK_SIZE;
		editBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, editBufferLen*sizeof(WCHAR));
		if(editBuffer == NULL) editBufferLen = 0;
	}
	if(editBuffer != NULL) {
		memset(editBuffer, 0, editBufferLen*sizeof(WCHAR));
		const BYTE *srcStart = fileBuffer;
		WCHAR *destStart = editBuffer;
		ConversionResult cvres = ConvertUTF8toUTF16(&srcStart, srcStart+bytesRead, &destStart, destStart+bytesRead, lenientConversion);
		if(cvres == conversionOK) {
			SendMessage(hwndEdit, WM_SETTEXT, 0, (LPARAM)editBuffer);
		}
	}
#else
	SendMessage(hwndEdit, WM_SETTEXT, 0, (LPARAM)fileBuffer);
#endif
	PostMessage(hwndEdit, WM_VSCROLL, SB_BOTTOM, (LPARAM)NULL);
	PostMessage(hwndEdit, WM_SETREDRAW, (WPARAM)TRUE, 0);
	working = FALSE;
}

BOOL OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
	hwndButton = CreateWindow(TEXT("button"),
							TEXT("clear"),
							WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON,
							CW_USEDEFAULT, CW_USEDEFAULT,
							CW_USEDEFAULT, CW_USEDEFAULT,
							hwnd, (HMENU)ID_BUTTON,
							lpcs->hInstance, lpcs) ;
	hwndEdit = CreateWindow(TEXT("EDIT"), NULL,
							WS_CHILD|WS_HSCROLL|WS_VSCROLL|WS_VISIBLE|ES_MULTILINE,
							CW_USEDEFAULT, CW_USEDEFAULT,
							CW_USEDEFAULT, CW_USEDEFAULT,
							hwnd, (HMENU)ID_EDIT,
							lpcs->hInstance, lpcs);
	if(hwndEdit == NULL) return FALSE;

	HRESULT res = SHGetFolderPath(hwnd, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, logFullpath);
	if(res != S_OK) return FALSE;
	_tcsncat(logFullpath, logPath, MAX_PATH-1-_tcslen(logFullpath)); logFullpath[MAX_PATH-1]=0;
	_tcsncpy(logPathname, logFullpath, MAX_PATH-1);
	_tcsncat(logPathname, logName, MAX_PATH-1-_tcslen(logPathname));
	logPathname[MAX_PATH-1]=0;
	hmon = StartMonitoring(logFullpath,
			FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_FILE_NAME,
			FileCallback);

	FileCallback(logPathname, 0, 0);

	return TRUE;
}
void OnResize(HWND hwnd, UINT state, short cx, short cy)
{
	int cyChar = HIWORD(GetDialogBaseUnits());
	MoveWindow(hwndEdit, 0, 0, cx, cy-cyChar, TRUE);
	MoveWindow(hwndButton, 0, cy-cyChar, cx, cyChar, TRUE);
}
void OnCommand(HWND hwnd, int id, HWND hwndCtl, UINT codeNotify)
{
	if(id == ID_BUTTON) {
		HANDLE hFile = CreateFile(logPathname,
								GENERIC_WRITE,
								0,
								NULL,
								TRUNCATE_EXISTING,
								FILE_ATTRIBUTE_NORMAL,
								NULL);
		if(hFile) CloseHandle(hFile);
	}
}
void OnDestroy(HWND hwnd)
{
	if(fileBuffer != NULL) HeapFree(GetProcessHeap(), 0, fileBuffer);
	if(editBuffer != NULL) HeapFree(GetProcessHeap(), 0, editBuffer);
	if(hmon) StopMonitoring(hmon);

	PostQuitMessage(0);
}


LRESULT CALLBACK WndProc(HWND hwnd, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
    switch (uiMsg)
    {
        HANDLE_MSG(hwnd, WM_CREATE, OnCreate);
        HANDLE_MSG(hwnd, WM_SIZE, OnResize);
        HANDLE_MSG(hwnd, WM_COMMAND, OnCommand);
        HANDLE_MSG(hwnd, WM_DESTROY, OnDestroy);
    }

    return DefWindowProc(hwnd, uiMsg, wParam, lParam);
}

BOOL InitApp(HINSTANCE hinst)
{
    WNDCLASS wc = { 0 };

    wc.style         = 0;
    wc.lpfnWndProc   = WndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = hinst;
    wc.hIcon         = NULL;
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = TEXT("f9trace");

    if (!RegisterClass(&wc)) return FALSE;

    return TRUE;
}



int WINAPI WinMain(HINSTANCE hinst, HINSTANCE hinstPrev,
                   LPSTR lpCmdLine, int nShowCmd)
{
    HWND hwnd;
    int  result;

    if (!InitApp(hinst)) return 0;

    RECT rcWorkArea;
    SystemParametersInfo(SPI_GETWORKAREA, 0, &rcWorkArea, 0);
    int nWidth = 270;
    int nHeight = 300;
    hwnd = CreateWindowEx(
    		WS_EX_TOOLWINDOW|WS_EX_TOPMOST,
            TEXT("f9trace"),                      /* Class Name */
            TEXT("Flash 9 Trace"),                /* Title */
            WS_OVERLAPPEDWINDOW,                  /* Style */
            rcWorkArea.right-nWidth, rcWorkArea.bottom-nHeight,         /* Position */
            nWidth, nHeight,         /* Size */
            NULL,                                 /* Parent */
            NULL,                                 /* No menu */
            hinst,                                /* Instance */
            0);                                   /* No special parameters */

    ShowWindow(hwnd, nShowCmd);

    result = RunAPCMessageLoop();

    return result;
}
